package kevin.framework.basic.common.utils;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.LogFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;

import kevin.framework.basic.common.thread.DaoDBKeyHolder;

/**
 * @Description: 实现动态多数据源支持
 * @author kevin.huang
 * @date 2017年3月1日 下午2:56:00
 * @version v1.0.0
 */
public class DynamicDaoSupport extends SqlSessionDaoSupport {

	final static Logger logger = LoggerFactory.getLogger(DynamicDaoSupport.class);

	static DefaultListableBeanFactory acf;
	static BeanDefinitionRegistry beanDefinitionRegistry;

	static BeanDefinitionBuilder sessionFactorybdb;
	static ApplicationContext context;

	@Resource(name = "defaultSessionFactory")
	protected SqlSessionFactory defaultFactory;

	/**
	 * 切换到指定数据库
	 ***/
	protected void setDbKey(String key) {
		DaoDBKeyHolder.setKey(key);
	}

	protected String getDbKey() {
		return DaoDBKeyHolder.getKey();
	}

	/**
	 * 恢复到默认数据库
	 ***/
	protected void clearDbKey() {
		DaoDBKeyHolder.clear();
	}

	@Override
	protected void checkDaoConfig() {

	}

	/***
	 * 重写根据线程DBkey实现动态数据源
	 ***/
	@Override
	public SqlSession getSqlSession() {
		String key = DaoDBKeyHolder.getKey();
		if (StringUtils.isEmpty(key)) {
			return this.defaultFactory.openSession(true);
		}
		SqlSessionFactory facotry = null;
		try {
			facotry = (SqlSessionFactory) context.getBean("ssf_" + key);
		} catch (Exception e) {

		}
		if (facotry == null) {
			return this.defaultFactory.openSession(true);
		}
		return facotry.openSession(true);
	}

	/**
	 * @Description: 初始化多数据源
	 * @Description: TODO
	 * @param DataSourcelist
	 * @param ctx
	 * @param onCreated
	 */
	public static void initDataSource(List<DataSourceInfo> dbList, ApplicationContext ctx,
			IOnDataSourceCreated onCreated) {
		// dbList = null;
		if (dbList == null || dbList.size() == 0) {
			return;
		}
		context = ctx;
		acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
		beanDefinitionRegistry = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
		final List<DataSourceInfo> dbs = dbList;
		final IOnDataSourceCreated onCreatedEv = onCreated;
		try {
			// 采用线程去创建，防止影响系统启动
			new Thread(new Runnable() {
				List<DataSourceInfo> fail = new ArrayList<DataSourceInfo>();

				@Override
				public void run() {
					for (DataSourceInfo db : dbs) {
						try {
							int sourceType = db.getType();
							if (sourceType == 1) {
								String jndiName = null;
								Object jndi = db.getProperty("jndiName");
								if (jndi != null) {
									jndiName = String.valueOf(jndi);
								}
								if (StringUtils.isNotEmpty(jndiName)) {
									putJNDIDataSource(jndiName);
								}
							} else {
								createDataSource(db);
							}
							if (logger.isDebugEnabled()) {
								logger.debug("The dataSource [{}] init success!", db.toString());
							}
						} catch (Exception ex) {
							fail.add(db);
							logger.error("{}", ex);
						}
					}
					if (onCreatedEv != null) {
						onCreatedEv.onCreated(fail);
					}

				}
			}, "web-init-datasource-thread").start();
		} catch (Exception e) {
			logger.error("{}", e);
		}
	}

	private static void createDataSource(DataSourceInfo dsInfo) throws Exception {
		removeDataSource(dsInfo.getId());
		Map<String, Object> props = dsInfo.getProperties();
		String dataSourceClass = String.valueOf(props.get("dataSourceClass"));
		DataSource dataSource = createDruidDataSource(dsInfo);
		if (dataSource != null) {
			if (dataSourceClass.toLowerCase().indexOf("xa") > 0) { // xa跨库事务
				dataSource = createAtomikosDataSource(dsInfo, (DruidDataSource) dataSource);
			}
			createSesseionFactory(dsInfo.getId(), dataSource, dsInfo.getMybatisCfgClassPath(),
					dsInfo.getMapperCfgClassPath(), String.valueOf(props.get("dialect")));
		}
	}

	private static DruidDataSource createDruidDataSource(DataSourceInfo dsInfo) {

		String beanId = "dy_db_source_" + dsInfo.getId();
		Map<String, Object> props = dsInfo.getProperties();
		int initialSize = Integer.valueOf(String.valueOf(props.get("initialSize")));
		int minIdle = Integer.valueOf(String.valueOf(props.get("minIdle")));
		int maxActive = Integer.valueOf(String.valueOf(props.get("maxActive")));
		int maxWait = Integer.valueOf(String.valueOf(props.get("maxWait")));
		int timeBetweenEvictionRunsMillis = Integer.valueOf(String.valueOf(props.get("timeBetweenEvictionRunsMillis")));
		int minEvictableIdleTimeMillis = Integer.valueOf(String.valueOf(props.get("minEvictableIdleTimeMillis")));
		boolean testWhileIdle = Boolean.parseBoolean(String.valueOf(props.get("testWhileIdle")));
		boolean testOnBorrow = Boolean.parseBoolean(String.valueOf(props.get("testOnBorrow")));
		boolean testOnReturn = Boolean.parseBoolean(String.valueOf(props.get("testOnReturn")));
		String filters = String.valueOf(props.get("filters"));
		String dataSourceClass = String.valueOf(props.get("dataSourceClass"));
		String validationQuery = String.valueOf(props.get("validationQuery"));
		boolean removeAbandoned = Boolean.parseBoolean(String.valueOf(props.get("removeAbandoned")));
		int removeAbandonedTimeout = Integer.valueOf(String.valueOf(props.get("removeAbandonedTimeout")));

		BeanDefinitionBuilder druidDataSourceFactorybdb = BeanDefinitionBuilder.genericBeanDefinition(dataSourceClass);
		druidDataSourceFactorybdb.addPropertyValue("validationQuery", validationQuery);
		druidDataSourceFactorybdb.addPropertyValue("initialSize", initialSize);
		druidDataSourceFactorybdb.addPropertyValue("minIdle", minIdle);
		druidDataSourceFactorybdb.addPropertyValue("maxActive", maxActive);
		druidDataSourceFactorybdb.addPropertyValue("maxWait", maxWait);
		druidDataSourceFactorybdb.addPropertyValue("timeBetweenEvictionRunsMillis", timeBetweenEvictionRunsMillis);
		druidDataSourceFactorybdb.addPropertyValue("minEvictableIdleTimeMillis", minEvictableIdleTimeMillis);
		druidDataSourceFactorybdb.addPropertyValue("testWhileIdle", testWhileIdle);
		druidDataSourceFactorybdb.addPropertyValue("testOnBorrow", testOnBorrow);
		druidDataSourceFactorybdb.addPropertyValue("testOnReturn", testOnReturn);
		druidDataSourceFactorybdb.addPropertyValue("filters", filters);
		druidDataSourceFactorybdb.addPropertyValue("removeAbandoned", removeAbandoned);
		druidDataSourceFactorybdb.addPropertyValue("removeAbandonedTimeout", removeAbandonedTimeout);

		try {
			LogFilter logFilter = (LogFilter) context.getBean("dblogFilter");
			List<Filter> filterArrays = new ArrayList<Filter>();
			filterArrays.add(logFilter);
			druidDataSourceFactorybdb.addPropertyValue("proxyFilters", filterArrays);
		} catch (Exception e) {
		}
		AbstractBeanDefinition def = druidDataSourceFactorybdb.getBeanDefinition();

		String driverClass = dsInfo.getDriverClass();
		String url = dsInfo.getUrl();
		String userName = dsInfo.getUsername();
		String password = dsInfo.getPassword();
		druidDataSourceFactorybdb.addPropertyValue("driverClassName", driverClass);
		druidDataSourceFactorybdb.addPropertyValue("url", url);
		druidDataSourceFactorybdb.addPropertyValue("username", userName);
		druidDataSourceFactorybdb.addPropertyValue("password", password);

		acf.registerBeanDefinition(beanId, def);

		DruidDataSource bean = (DruidDataSource) context.getBean(beanId);

		try {
			Connection conn = bean.getConnection();
			if (!conn.isClosed()) {
				conn.close();
			}
		} catch (SQLException e) {
			return null;
		}
		return (bean);
	}

	private static AtomikosDataSourceBean createAtomikosDataSource(DataSourceInfo dsInfo, DruidDataSource druidSource) {

		String beanId = "atomikos_ds_" + dsInfo.getId();

		int maintenanceInterval = Integer.valueOf(String.valueOf(dsInfo.getxaProps("maintenanceInterval")));
		int poolSize = Integer.valueOf(String.valueOf(dsInfo.getxaProps("poolSize")));
		String testQuery = String.valueOf(dsInfo.getxaProps("testQuery"));

		BeanDefinitionBuilder atomikosSourceFactorybdb = BeanDefinitionBuilder
				.genericBeanDefinition("com.atomikos.jdbc.AtomikosDataSourceBean");
		AbstractBeanDefinition def = atomikosSourceFactorybdb.getBeanDefinition();
		atomikosSourceFactorybdb.addPropertyValue("uniqueResourceName", beanId);
		atomikosSourceFactorybdb.addPropertyValue("xaDataSource", druidSource);
		atomikosSourceFactorybdb.addPropertyValue("poolSize", poolSize);
		atomikosSourceFactorybdb.addPropertyValue("testQuery", testQuery);
		atomikosSourceFactorybdb.addPropertyValue("maintenanceInterval", maintenanceInterval);

		acf.registerBeanDefinition(beanId, def);
		AtomikosDataSourceBean bean = (AtomikosDataSourceBean) context.getBean(beanId);
		return bean;
	}

	/**
	 * @Description: 创建LocalSessionFactoryBean
	 * @return
	 * @throws Exception
	 */
	private static SqlSessionFactory createSesseionFactory(String beanId, DataSource dataSource,
			String mybatisCfgClassPath, String mapperCfgClassPath, String dialect) throws Exception {

		if (sessionFactorybdb == null) {
			sessionFactorybdb = BeanDefinitionBuilder.genericBeanDefinition("org.mybatis.spring.SqlSessionFactoryBean");
		}

		beanId = "ssf_" + beanId;
		AbstractBeanDefinition def = sessionFactorybdb.getBeanDefinition();

		PathMatchingResourcePatternResolver resoler = new PathMatchingResourcePatternResolver();
		org.springframework.core.io.Resource[] resources = (org.springframework.core.io.Resource[]) resoler
				.getResources(mybatisCfgClassPath);
		sessionFactorybdb.addPropertyValue("configLocation", resources);

		org.springframework.core.io.Resource[] resources1 = (org.springframework.core.io.Resource[]) resoler
				.getResources(mapperCfgClassPath);
		sessionFactorybdb.addPropertyValue("mapperLocations", resources1);

		com.github.pagehelper.PageInterceptor interceptor = new com.github.pagehelper.PageInterceptor();

		Properties prop = new Properties();
		prop.setProperty("helperDialect", dialect);
		prop.setProperty("msCountCache", "1000*60");

		Interceptor[] plugins = new Interceptor[] { interceptor };

		sessionFactorybdb.addPropertyValue("plugins", plugins);

		sessionFactorybdb.addPropertyValue("dataSource", dataSource);
		acf.registerBeanDefinition(beanId, def);
		Object bean = context.getBean(beanId);
		SqlSessionFactory factory = (SqlSessionFactory) bean;
		return factory;
	}

	/**
	 * @Description: 移除
	 * @param key
	 */
	public static boolean removeDataSource(String key) {
		boolean isOk = true;
		String beanName = "dy_db_source_" + key;
		/** 移除DruidDataSource **/
		try {
			if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
				beanDefinitionRegistry.removeBeanDefinition(beanName);
			}
		} catch (Exception e) {

		}
		/** 移除AtomikosDataSource ***/
		beanName = "atomikos_ds_" + key;
		try {
			if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
				beanDefinitionRegistry.removeBeanDefinition(beanName);
			}
		} catch (Exception e) {

		}
		/** 移除spring中的SqlSessionFactory **/
		beanName = "ssf_" + key;
		try {
			if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
				beanDefinitionRegistry.removeBeanDefinition(beanName);
			}
		} catch (Exception e) {

		}
		return isOk;
	}

	/**
	 * @Description: 测试数据源是否可用
	 * @param domainId
	 * @return
	 * @throws Exception
	 */
	public static boolean testDataSource(String id) throws Exception {
		boolean isOk = false;
		String beanId = "dy_db_source_" + id;
		try {
			DataSource dataSource = (DataSource) context.getBean(beanId);
			Connection conn = dataSource.getConnection();
			if (!conn.isClosed()) {
				conn.close();
			}
			isOk = true;
		} catch (Exception ex) {
			logger.debug("testDataSource {}", ex);
		}
		return isOk;
	}

	/**
	 * @Description: 添加JNDI数据源
	 * @param jndiName
	 * @param domainId
	 * @return
	 */
	private static boolean putJNDIDataSource(String jndiName) {
		return true;
	}

}
